package com.capsulode.excel.header;

import com.capsulode.excel.conversion.ConverterManager;
import com.capsulode.excel.conversion.Reader;
import com.capsulode.excel.exception.*;
import org.apache.poi.ss.usermodel.Cell;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public abstract class AbstractHeader<R, F> implements Header<R> {
    private final String name;
    private Integer index;

    AbstractHeader(String name) {
        this.name = name;
    }


    @Override
    public String getName() {
        return name;
    }


    @Override
    public int getIndex() {
        if (index == null) {
            throw new IllegalStateException("Header's column index unset!");
        }
        return index;
    }

    @Override
    public void setIndex(int index) {
        this.index = index;
    }

    @Override
    public void read(Cell cell, R row) throws UnsupportedConversionException, CellTypeNotExpectException, ConversionFailException, ValidationFailedException, ReadingProcessFailedException {
        if (cell == null) {
            validate(null);
            read((F) null, row);
            return;
        }

        F field = convert(cell, getCachedFieldClass());
        validate(field);
        read(field, row);
    }

    /**
     * Fill `RowObject` with field value;
     *
     * @param f   field value
     * @param row a `JavaBean` represents row data.
     */
    protected abstract void read(F f, R row) throws ReadingProcessFailedException;

    /**
     * Validate field value. Default implements NOTHING.
     *
     * @throws IllegalStateException validation failed.
     */
    protected void validate(F value) throws ValidationFailedException {

    }

    /**
     * Define what type the field is. Used by {@link #convert(Cell, Class)} to convert origin cell to `JavaBean` field type.
     * <p>
     * <p>This default implements reflects `F` value defined by subclass.</p>
     */
    protected Class<F> type() {
        Type[] type = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments();
        if (type.length < 2) {
            throw new IllegalStateException("Can not determine the field type!");
        }
        return (Class<F>) type[1];
    }

    /**
     * Convert cell value to specified type.
     *
     * @param cell current cell. Not null,null cell just call {@link #read(F, R) read(null,row)} and doesn't come here.
     * @param type the type will convert to.
     * @return converted value.
     * @throws UnsupportedConversionException can not convert cell value to specified type.
     */
    protected F convert(Cell cell, Class<F> type) throws UnsupportedConversionException, ConversionFailException, CellTypeNotExpectException {
        Reader<F> reader = ConverterManager.newConverter(type, null);
        return reader.convert(cell);
    }

    private Class<F> cachedFieldType;

    private Class<F> getCachedFieldClass() {
        if (cachedFieldType == null) {
            cachedFieldType = type();
        }
        return cachedFieldType;
    }

    @Override
    public String toString() {
        return String.format("%s[%s]", name, getCachedFieldClass().getCanonicalName());
    }
}
